﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Threading;
using KeyenceCl3000.Native;
using WPFCommon.Interface;
using WPFCommon.ViewModels.Base;

namespace KeyenceCl3000
{
    public sealed class KeyenceCl3000Controller : ViewModelBase
    {
        #region private fields

        /// <summary>
        /// Buffer count to receive one sample of output data from device
        /// </summary>
        private const int MaxRequestDataLength = 1024;
        /// <summary>
        /// Handles traffics to query output from device
        /// </summary>
        private readonly object _deviceLocker = new object();
        /// <summary>
        /// Log exceptions to client code
        /// </summary>
        private readonly IMessageLogger _messageLogger;
        private readonly byte[] _ipAddress = new byte[4];
        /// <summary>
        /// Timer to retrieve output to ui
        /// </summary>
        private DispatcherTimer _dispatcherTimer;
        /// <summary>
        /// Relay data to ui
        /// </summary>
        private IDataProvider[] _dataProviders;
        private uint _triggerCount;
        private int _pulseCount;
        private bool _isConnected;

        #endregion

        #region ctor

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ipAddress"></param>
        /// <param name="messageLogger">Log exceptions to client code</param>
        /// <param name="dataProviders">Relay data to ui</param>
        /// <param name="uiRetrieveInterval">Interval to log data to ui</param>
        public KeyenceCl3000Controller(byte[] ipAddress, IMessageLogger messageLogger,  IDataProvider[] dataProviders,  double uiRetrieveInterval = 200)
        {
            for (int i = 0; i < _ipAddress.Length; i++)
            {
                _ipAddress[i] = ipAddress[i];
            }

            DeviceId = _ipAddress[3];

            _messageLogger = messageLogger;
            _dataProviders = dataProviders;

            _dispatcherTimer = new DispatcherTimer()
            {
                Interval = TimeSpan.FromMilliseconds(uiRetrieveInterval)
            };
            _dispatcherTimer.Tick += RetrieveUiData;
            _dispatcherTimer.Start();
        }



        #endregion


        #region api

        /// <summary>
        /// Get one sample of output from the controller
        /// </summary>
        /// <returns>One sample of output items for each OUT of the controller</returns>
        public OutItem[] QueryOutItems()
        {
            byte[] buffer = new byte[MaxRequestDataLength];
            using (PinnedObject pin = new PinnedObject(buffer))
            {
                CL3IF_MEASUREMENT_DATA measurementData = new CL3IF_MEASUREMENT_DATA();
                measurementData.outMeasurementData = new CL3IF_OUTMEASUREMENT_DATA[NativeMethods.CL3IF_MAX_OUT_COUNT];

                int returnCode;
                lock (_deviceLocker)
                {
                    returnCode = NativeMethods.CL3IF_GetMeasurementData(DeviceId, pin.Pointer);
                }
                if (returnCode != NativeMethods.CL3IF_RC_OK)
                {
                    Log("GetMeasurementData failed");
                    return null;
                }

                measurementData.addInfo = (CL3IF_ADD_INFO) Marshal.PtrToStructure(pin.Pointer, typeof(CL3IF_ADD_INFO));
                int readPosition = Marshal.SizeOf(typeof(CL3IF_ADD_INFO));
                for (int i = 0; i < NativeMethods.CL3IF_MAX_OUT_COUNT; i++)
                {
                    measurementData.outMeasurementData[i] =
                        (CL3IF_OUTMEASUREMENT_DATA) Marshal.PtrToStructure(pin.Pointer + readPosition,
                            typeof(CL3IF_OUTMEASUREMENT_DATA));
                    readPosition += Marshal.SizeOf(typeof(CL3IF_OUTMEASUREMENT_DATA));
                }


                TriggerCount = measurementData.addInfo.triggerCount;
                PulseCount = measurementData.addInfo.pulseCount;

                OutItem[] outItems = new OutItem[NativeMethods.CL3IF_MAX_OUT_COUNT];

                for (int i = 0; i < NativeMethods.CL3IF_MAX_OUT_COUNT; i++)
                {
                    outItems[i] = new OutItem()
                    {
                        Value = measurementData.outMeasurementData[i].measurementValue,
                        ValueInfo = (CL3IF_VALUE_INFO) measurementData.outMeasurementData[i].valueInfo,
                        JudgeResult = measurementData.outMeasurementData[i].judgeResult
                    };
                }

                return outItems;
            }
        }
        
        public bool Connect()
        {
            CL3IF_ETHERNET_SETTING ethernetSetting = new CL3IF_ETHERNET_SETTING
            {
                ipAddress = _ipAddress, reserved = new byte[2], portNo = PortNo
            };
            ethernetSetting.reserved[0] = 0x00;
            ethernetSetting.reserved[1] = 0x00;
            int returnCode = NativeMethods.CL3IF_OpenEthernetCommunication(DeviceId, ref ethernetSetting, 10000);

            IsConnected = returnCode == NativeMethods.CL3IF_RC_OK;

            return IsConnected;
        }
        
        public void Disconnect()
        {
            int returnCode = NativeMethods.CL3IF_CloseCommunication(DeviceId);

            IsConnected = false;
        }

        #endregion


        #region impl

        private void RetrieveUiData(object sender, EventArgs e)
        {
            if (_dataProviders == null) return;

            var outItems = QueryOutItems();
            for (int i = 0; i < _dataProviders.Length; i++)
            {
                //TODO: handle invalid data
                var dataProvider = (OutValueProvider) _dataProviders[i];
                dataProvider.SetData(outItems[i].Value);
            }
        }
        private void Log(string message)
        {
            _messageLogger?.Log(message);
        }

        #endregion

        #region props

        public int DeviceId { get; private set; }

        public ushort PortNo { get; set; } = 24685;

        public uint TriggerCount
        {
            get => _triggerCount;
            private set
            {
                _triggerCount = value;
                OnPropertyChanged();
            }
        }

        public int PulseCount
        {
            get => _pulseCount;
            private set
            {
                _pulseCount = value;
                OnPropertyChanged();
            }
        }

        public bool IsConnected
        {
            get => _isConnected;
            private set
            {
                _isConnected = value;
                OnPropertyChanged();
            }
        }

        #endregion
    }
}